//
//  AdaptivePresentationController.swift
//  CustomTransitions
//
//  Created by fuyoufang on 2021/10/13.
//

import Foundation
import UIKit

class AdaptivePresentationController: UIPresentationController {
    
    struct Containt {
        static let dismissButtonWidth = 26
        static let dismissButtonHeight = 26
    }
    
    var dismissButton: UIButton?
    var presentationWrappingView: UIView?
    
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        presentedViewController.modalPresentationStyle = .custom
    }
    
    override var presentedView: UIView? {
        // Return the wrapping view created in -presentationTransitionWillBegin.
        return presentationWrappingView
    }
    
    //| ----------------------------------------------------------------------------
    //  This is one of the first methods invoked on the presentation controller
    //  at the start of a presentation.  By the time this method is called,
    //  the containerView has been created and the view hierarchy set up for the
    //  presentation.  However, the -presentedView has not yet been retrieved.
    //
    override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()
        // The default implementation of -presentedView returns
        // self.presentedViewController.view.
        let presentedViewControllerView = super.presentedView
        
        // Wrap the presented view controller's view in an intermediate hierarchy
        // that applies a shadow and adds a dismiss button to the top left corner.
        //
        // presentationWrapperView              <- shadow
        //     |- presentedViewControllerView (presentedViewController.view)
        //     |- close button
        let presentationWrappingView = UIView().then {
            $0.layer.do {
                $0.shadowOpacity = 0.63
                $0.shadowRadius = 17
            }
            $0.frame = frameOfPresentedViewInContainerView
        }
        self.presentationWrappingView = presentationWrappingView
        
        if let presentedViewControllerView = presentedViewControllerView {
            // Add presentedViewControllerView -> presentationWrapperView.
            presentedViewControllerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            presentationWrappingView.addSubview(presentedViewControllerView)
        }
        
        // Create the dismiss button.
        let dismissButton = UIButton().then {
            $0.setImage(UIImage(named: "CloseButton"), for: .normal)
            $0.frame = CGRect(x: 0,
                              y: 0,
                              width: Containt.dismissButtonWidth,
                              height: Containt.dismissButtonHeight)
            $0.addTarget(self, action: #selector(dismissButtonAction(sender:)), for: .touchUpInside)
        }
        self.dismissButton = dismissButton
        
        // Add dismissButton -> presentationWrapperView.
        presentationWrappingView.addSubview(dismissButton)
    }
    
    // MARK: - Dismiss Button
    
    //| ----------------------------------------------------------------------------
    //  IBAction for the dismiss button.  Dismisses the presented view controller.
    //
    @objc func dismissButtonAction(sender: Any) {
        presentedViewController.dismiss(animated: true, completion: nil)
    }
    
    // MARK: - Layout
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        #warning("旋转的过程中，注意处理阴影")
        presentationWrappingView?.do {
            $0.clipsToBounds = true
            $0.layer.do {
                $0.shadowRadius = 0
                $0.shadowOpacity = 0
            }
        }
        
        coordinator.animate { context in
            
        } completion: { [weak self] context in
            self?.presentationWrappingView?.do {
                $0.clipsToBounds = false
                $0.layer.do {
                    $0.shadowRadius = 17
                    $0.shadowOpacity = 0.63
                }
            }
        }
    }
    
    //| ----------------------------------------------------------------------------
    //  When the presentation controller receives a
    //  -viewWillTransitionToSize:withTransitionCoordinator: message it calls this
    //  method to retrieve the new size for the presentedViewController's view.
    //  The presentation controller then sends a
    //  -viewWillTransitionToSize:withTransitionCoordinator: message to the
    //  presentedViewController with this size as the first argument.
    //
    //  Note that it is up to the presentation controller to adjust the frame
    //  of the presented view controller's view to match this promised size.
    //  We do this in -containerViewWillLayoutSubviews.
    //
    override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
        if let viewController = container as? UIViewController,
            viewController == self.presentedViewController {
            return CGSize(width: parentSize.width / 2, height: parentSize.height / 2)
        } else {
            return super.size(forChildContentContainer: container, withParentContainerSize: parentSize)
        }
    }
    
    override var frameOfPresentedViewInContainerView: CGRect {
        let containerViewBounds = containerView?.bounds ?? .zero
        let size = size(forChildContentContainer: self.presentedViewController,
                        withParentContainerSize: self.containerView?.bounds.size ?? .zero)
        // Center the presentationWrappingView view within the container.
    
        // 下面两种结算 frame 的方法一样，Apple 采用的是第二种
//        let frame = CGRect(x: (containerViewBounds.size.width - size.width) / 2,
//                           y: (containerViewBounds.size.height - size.height) / 2,
//                           width: size.width,
//                           height: size.height)
        let frame = CGRect(x: containerViewBounds.midX - size.width / 2,
                           y: containerViewBounds.midY - size.height / 2,
                           width: size.width,
                           height: size.height)
        
        // Outset the centered frame of presentationWrappingView so that the
        // dismiss button is within the bounds of presentationWrappingView.
        // 向上、向左偏移了 20
        // 实际的内容区域相较于原来扩大了 (dx * 2, dy * 2)
        return frame.insetBy(dx: -20, dy: -20)
    }
    
    //| ----------------------------------------------------------------------------
    //  This method is similar to the -viewWillLayoutSubviews method in
    //  UIViewController.  It allows the presentation controller to alter the
    //  layout of any custom views it manages.
    //
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        
        if let presentationWrappingView = presentationWrappingView {
            presentationWrappingView.frame = frameOfPresentedViewInContainerView
            
            // Undo the outset that was applied in -frameOfPresentedViewInContainerView.
            presentedViewController.view.frame = presentationWrappingView.bounds
                .insetBy(dx: 20, dy: 20)
            
            // Position the dismissButton above the top-left corner of the presented
            // view controller's view.
            dismissButton?.center = CGPoint(x: presentedViewController.view.frame.minX,
                                            y: presentedViewController.view.frame.minY)
        }
    }
}

extension AdaptivePresentationController: UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return transitionContext?.isAnimated ?? false ? 0.35 : 0
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let toViewController = transitionContext.viewController(forKey: .to),
              let fromViewController = transitionContext.viewController(forKey: .from) else {
                  transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
                  return
              }
        //let toViewInitialFrame = transitionContext.initialFrame(for: toViewController)
        let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
        let fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
        
        let toView = transitionContext.view(forKey: .to)
        
        let fromView = transitionContext.view(forKey: .from)
        
        let containerView = transitionContext.containerView
        
        let isPresenting = toViewController.presentingViewController == fromViewController
        
#warning("""
Apple 的 demo，不管 isPresentation 是否为 true，
都会执行 containerView.addSubview(toView)

但是，我感觉只有 isPresentation 为 true 时，才需要添加
""")
        if let toView = toView {
            containerView.addSubview(toView)
        }
        if isPresenting {
//            if let toView = toView {
//                containerView.addSubview(toView)
//            }

            // This animation only affects the alpha.  The views can be set to
            // their final frames now.
            fromView?.frame = fromViewFinalFrame
            toView?.do {
                $0.frame = toViewFinalFrame
                $0.alpha = 0
            }
        } else {
            // Because our presentation wraps the presented view controller's view
            // in an intermediate view hierarchy, it is more accurate to rely
            // on the current frame of fromView than fromViewInitialFrame as the
            // initial frame.
            toView?.frame = toViewFinalFrame
        }
        
        let transitionDuration = transitionDuration(using: transitionContext)
        UIView.animate(withDuration: transitionDuration) {
            if isPresenting {
                toView?.alpha = 1
            } else {
                fromView?.alpha = 0
            }
        } completion: { finished in
            // When we complete, tell the transition context
            // passing along the BOOL that indicates whether the transition
            // finished or not.
            let wasCancelled = transitionContext.transitionWasCancelled
            transitionContext.completeTransition(!wasCancelled)
            
            // Reset the alpha of the dismissed view, in case it will be used
            // elsewhere in the app.
            #warning("注意：动画的时候调整了 fromView 的 alpha, 转场结束之后要进行恢复")
            if !isPresenting {
                fromView?.alpha = 1
            }
        }
    }
}

extension AdaptivePresentationController: UIViewControllerTransitioningDelegate {
   
    
    //| ----------------------------------------------------------------------------
    //  If the modalPresentationStyle of the presented view controller is
    //  UIModalPresentationCustom, the system calls this method on the presented
    //  view controller's transitioningDelegate to retrieve the presentation
    //  controller that will manage the presentation.  If your implementation
    //  returns nil, an instance of UIPresentationController is used.
    //
    #warning("""
    如果不设置 presentedViewController.modalPresentationStyle = .custom，
    会导致下面的方法不调用
    """)
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
#warning("""
UIPresentationController 的 presentationController(forPresented presented:, presenting: , source: ) 方法，
只能处理 self.presentedViewController 和 presented 相同的情况，
因为 UIPresentationController 会影响到 presented 的最终位置 和 presentedView 值。

但是，animationController(forPresented presented:,presenting:,source:) 和 animationController(forDismissed dismissed:) 方法可以处理所有的情况，
因为这个两个方法只会影响的动画的效果，不会影响最终结果。

""")
        assert(presentedViewController == presented, "You didn't initialize \(self) with the correct presentedViewController.  Expected \(presented), got \(presentedViewController).")
        return self
    }
    
    //| ----------------------------------------------------------------------------
    //  The system calls this method on the presented view controller's
    //  transitioningDelegate to retrieve the animator object used for animating
    //  the presentation of the incoming view controller.  Your implementation is
    //  expected to return an object that conforms to the
    //  UIViewControllerAnimatedTransitioning protocol, or nil if the default
    //  presentation animation should be used.
    //
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }
    
    //| ----------------------------------------------------------------------------
    //  The system calls this method on the presented view controller's
    //  transitioningDelegate to retrieve the animator object used for animating
    //  the dismissal of the presented view controller.  Your implementation is
    //  expected to return an object that conforms to the
    //  UIViewControllerAnimatedTransitioning protocol, or nil if the default
    //  dismissal animation should be used.
    //
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }
}
